from datetime import datetime
import logging
import re
import threading
import time
from hdc import Hdc
from keycode import KeyCode

class UITest:
    def __init__(self):
        pass

    # UITest uiInput ...   
    class UIInput:
        def __init__(self, hdc: Hdc, device_id: str = ""):
            self.hdc = hdc
            self.device_id = device_id
            self.module = "uitest uiInput"

        def click(self, x: int, y: int):
            self.hdc.shell(self.device_id, f"{self.module} click {x} {y}")

        def double_click(self, x: int, y: int):
            self.hdc.shell(self.device_id, f"{self.module} doubleClick {x} {y}")

        def long_click(self, x: int, y: int):
            self.hdc.shell(self.device_id, f"{self.module} longClick {x} {y}")

        def swipe(self, x1: int, y1: int, x2: int, y2: int, velocity: int = 600):
            """
            Swipe from one point to another point
            
            Args:
                self.device_id (str): Device ID
                x1 (int): x coordinate of the start point
                y1 (int): y coordinate of the start point
                x2 (int): x coordinate of the end point
                y2 (int): y coordinate of the end point
                velocity (int): Swipe velocity(pixels per second), velocity ranges from 200 to 40000, default 600
            """
            self.hdc.shell(self.device_id, f"{self.module} swipe {x1} {y1} {x2} {y2} {velocity}")

        def drag(self, x1: int, y1: int, x2: int, y2: int, velocity: int = 600):
            """
            Drag from one point to another point

            Args:
                self.device_id (str): Device ID
                x1 (int): x coordinate of the start point
                y1 (int): y coordinate of the start point
                x2 (int): x coordinate of the end point
                y2 (int): y coordinate of the end point
                velocity (int): Drag velocity(pixels per second), velocity ranges from 200 to 40000, default 600
            """
            self.hdc.shell(self.device_id, f"{self.module} drag {x1} {y1} {x2} {y2} {velocity}")

        def intput_text(self, x: int, y: int, text: str):
            """
            Input text

            Args:
                self.device_id (str): Device ID
                x (int): x coordinate
                y (int): y coordinate
                text (str): Text to input
            """
            self.hdc.shell(self.device_id, f"{self.module} inputText {x} {y} {text}")
        
        def input_focused_text(self, text: str):
            """
            Input text at focus

            Args:
                self.device_id (str): Device ID
                text (str): Text to input
            """
            self.hdc.shell(self.device_id, f"{self.module} text {text}")

        def home(self):
            self.hdc.shell(self.device_id, f"{self.module} keyEvent {KeyCode.KEYCODE_HOME.value[0]}")

        def back(self):
            self.hdc.shell(self.device_id, f"{self.module} keyEvent {KeyCode.KEYCODE_BACK.value[0]}")

        def power(self):
            self.hdc.shell(self.device_id, f"{self.module} keyEvent {KeyCode.KEYCODE_POWER.value[0]}")

        def key_event(self, key_code: KeyCode):
            """
            keyEvent <keyID/Back/Home/Power>  inject keyEvent
            """
            self.hdc.shell(self.device_id, f"{self.module} keyEvent {key_code.value[0]}")

        def key_event_ex(self, key_code0: KeyCode, key_code1: KeyCode, key_code2: KeyCode = None):
            """
            keyEvent <keyID_0> <keyID_1> [keyID_2]   keyID_2 default to None
            """
            cmd = f"{self.module} keyEvent {key_code0.value[0]} {key_code1.value[0]}" \
                if key_code2 is None \
                else f"{self.module} keyEvent {key_code0.value[0]} {key_code1.value[0]} {key_code2.value[0]}"
            self.hdc.shell(self.device_id, cmd)

        def fling(self, x0: int, y0: int, x1: int, y1: int, velocity: int, step: int):
            cmd = f"{self.module} fling {x0} {y0} {x1} {y1} {velocity} {step}"
            self.hdc.shell(self.device_id, cmd)

        def direction_fling(self, direction: int, velocity: int, step: int):
            """
            direction_fling

            Args:
                direction: 0,1,2,3(left, right, up, down)
                velocity: velocity ranges from 200 to 40000, default 600
                step: 1-100
            """
            cmd = f"{self.module} dircFling {direction} {velocity} {step}"
            self.hdc.shell(self.device_id, cmd)

    # UITest dumpLayout ...
    class DumpLayout:
        def __init__(self, hdc: Hdc, device_id: str = ""):
            self.module = "uitest dumpLayout"
            self.rootFolder = "/data/local/tmp"
            self.hdc = hdc
            self.device_id = device_id
            self.options = dict({
                # specifies the savePath
                "-p": { "enable": True, "value": "/data/local/tmp/layout.json" },
                # not merge windows and filter nodes
                "-i": { "enable": False, "value": None },
                # include font attributes
                "-a": { "enable": False, "value": None },
                # specifies the bundleName of the target window
                "-b": { "enable": False, "value": None },
                # specifies the window id of the target window
                "-w": { "enable": False, "value": None },
                # whether merge windows, true means to merge, set it ture when not use this option
                "-m": { "enable": False, "value": None },
                # specifies the locate screen of the target window
                "-d": { "enable": False, "value": None },
            })
        
        def set_option(self, option: str, enable: bool, value: any):
            """
            Set a specific option for DumpLayout command.

            Args:
                option (str): Option name, e.g., "-p", "-i", "-a"
                enable (bool): Whether to enable this option
                value (str, optional): The value of the option, if required.
            """
            self.options[option] = {"enable": enable, "value": value}

        def get_options(self) -> str:
            result = []
            for option, option_info in self.options.items():
                if option_info["enable"]:
                    result.append(option if option_info["value"] is None \
                                   else f"{option} {option_info['value']}")
            logging.debug(f"DumpLayout Options: {result}")
            return " ".join(result).strip()

        def clear(self):
            """
            delete all layout json files on device
            """
            self.hdc.shell(self.device_id, f"rm -rf {self.rootFolder}/layout_*.json")

        def dump(self) -> str:
            self.set_option("-p", True, f"{self.rootFolder}/layout_{datetime.now().strftime('%Y%m%d%H%M%S')}.json")
            options = self.get_options()
            cmd = f"{self.module} {options}"
            result = self.hdc.shell(self.device_id, cmd)
            logging.debug(f"DumpLayout command: {cmd}, reuslt: {result}")
            pattern = r"DumpLayout saved to:(?P<path>[^\r\n\f]+.json)"
            match = re.search(pattern, result)
            if match:
                real_path = match.group("path")
                logging.debug(f"Success to dump layout json: {real_path}")
                return real_path
            else:
                logging.error(f"Failed to dump layout json: {self.options["-p"]["value"]}")
                return None
        def dump_recv(self, local_file: str) -> bool:
            path = self.dump()
            if path is not None:
                result = self.hdc.file_recv(self.device_id, path, local_file)
                return result["error"]["code"] == 0
            else:
                return False

    # UITest screenCap ...
    class ScreenCapture:
        def __init__(self, hdc: Hdc, device_id: str):
            self.module = "uitest screenCap"
            self.rootFolder = "/data/local/tmp"
            self.hdc = hdc
            self.device_id = device_id
            self.ext = "jpeg"
            self.options = dict(
                {
                    # specifies the savePath
                    "-p": { "enable": True, "value": f"/data/local/tmp/screen.{self.ext}"}
                }
            )

        def set_option(self, option: str, enable: bool, value: any):
            """
            Set a specific option for screen capture command.

            Args:
                option (str): Option name, e.g., "-p"
                enable (bool): Whether to enable this option
                value (str, optional): The value of the option, if required.
            """
            self.options[option] = {"enable": enable, "value": value}

        def get_options(self) -> str:
            result = []
            for option, option_info in self.options.items():
                if option_info["enable"]:
                    result.append(option if option_info["value"] is None \
                                   else f"{option} {option_info['value']}")
            logging.debug(f"ScreenCapture Options: {result}")
            return " ".join(result).strip()

        def clear(self):
            """
            delete all screenshot pictures on device
            """
            self.hdc.shell(self.device_id, f"rm -rf {self.rootFolder}/screen_*.{self.ext}")

        def capture(self) -> bool:
            self.set_option("-p", True, f"{self.rootFolder}/screen_{datetime.now().strftime('%Y%m%d%H%M%S')}.{self.ext}")
            print(f"options: {self.options["-p"]}")
            options = self.get_options()
            cmd = f"{self.module} {options}"
            result = self.hdc.shell(self.device_id, cmd)
            logging.debug(f"ScreenCap command: {cmd}, reuslt: {result}")
            pattern = rf"ScreenCap saved to\s+(?P<path>[^\r\n\f]+\.{self.ext})"
            match = re.search(pattern, result)
            if match:
                real_path = match.group("path")
                logging.debug(f"Success to screen capture: {real_path}")
                return real_path
            else:
                logging.error(f"Failed to screen capture: {self.options["-p"]["value"]}")
                return None
        
        def capture_recv(self, local_file: str) -> bool:
            """
            Receive screen capture
            
            Args:
                local_file (str): Local file path

            Returns:
                bool: True if success, False otherwise
            """
            path = self.capture()
            if path is not None:
                result = self.hdc.file_recv(self.device_id, path, local_file)
                return result["error"]["code"] == 0
            else:
                return False            

    # UITest uiRecord
    class UIRecord:
        def __init__(self, hdc: Hdc, device_id: str, encoding = "utf-8"):
            self.module = "uitest uiRecord"
            self.record_csv = "/data/local/tmp/record.csv"
            self.hdc = hdc
            self.device_id = device_id
            self.is_recording = False
            self.encoding = encoding  # default encoding is utf-8

        def clear(self):
            """
            clear record.csv
            """
            logging.info(f"[uitest-uiRecord] clear ${self.record_csv}.")
            self.hdc.shell_no_return(self.device_id, f"rm -rf {self.record_csv}")
        
        def start(self):
            """
            start uiRecord
            """
            logging.info(f"[uitest-uiRecord] start.")
            self.hdc.shell_no_return(self.device_id, f"{self.module} record")

        def stop(self):
            """
            stop uiRecord
            """
            pid = self.hdc.get_pid(self.device_id, "uitest uiRecord record")
            if pid != -1:
                logging.info(f"[uitest-uiRecord] stop, pid: {pid}.")
                self.hdc.kill_pid(self.device_id, pid)

        def record_for_seconds(self, duration_seconds: int = 30):
            """
            Record for a specified number of seconds.
            
            Args:
                duration_seconds (int): The number of seconds to record. Defaults to 30.
            """
            logging.info(f"It is recording on device for {duration_seconds} seconds, please perform operations on the app interface.")
            thread = threading.Thread(target=self.start)
            thread.start()
            time.sleep(duration_seconds)
            self.stop()

        def get_record_info(self):
            return self.hdc.shell(self.device_id, f"{self.module} read")
        
        def save_record_info(self, local_file: str):
            result = self.get_record_info()
            """
            save record info to local file
            """
            with open(local_file, "w+", encoding=self.encoding) as f:
                f.write(result)
                f.flush()
            logging.info(f"Save uiRecord info to {local_file}")